//---------------------------------------------------------------------
// File: SOAPHTTPRequestResponseStep.cs
// 
// Summary: 
//
//---------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------

namespace Microsoft.Services.BizTalkApplicationFramework.BizUnit.Extensions
{
	using System;
	using System.IO;
	using System.Xml;
	using System.Net;
	using System.Reflection;
	using System.Xml.Serialization;
	using System.Web.Services.Discovery;
	using System.Web.Services.Protocols;
	using Microsoft.CSharp;
	using System.CodeDom;
	using System.CodeDom.Compiler;
	using System.Web.Services.Description;

	/// <summary>
	/// The SOAPHTTPRequestResponseStep test step may be used to call a Web Service and optionally validate it's response.
	/// Note that this cannot be used against an untyped webservice (ie) which takes an XmlDocument as input
	/// </summary>
	/// 
	/// <remarks>
	/// The following shows an example of the Xml representation of this test step.
	/// 
	/// <code escaped="true">
	///	<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.SOAPHTTPRequestResponseStep">
	///		<WebServiceWSDLURL>http://machine/virdir/StockQuoteService.aspx?wsdl</WebServiceWSDLURL>
	///		<ServiceName>Samples_StockQuoteService</ServiceName>
	///		<WebMethod>GetQuote</WebMethod>
	///		<InputMessageTypeName>QuoteRequest</InputMessageTypeName>
	///		<MessagePayload>.\TestData\RequestMSFTQuote.xml</MessagePayload>
	///		
	///		<ValidationStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.XmlValidationStep">
	///			<XPathList>
	///				<XPathValidation query="/StockQuote/Symbol">MSFT</XPathValidation>
	///				<XPathValidation query="/StockQuote/LastPrice">35.36</XPathValidation>
	///			</XPathList>
	///		</ValidationStep>
	///	</TestStep>
	///	</code>
	///	
	///	<list type="table">
	///		<listheader>
	///			<term>Tag</term>
	///			<description>Description</description>
	///		</listheader>
	///		<item>
	///			<term>WebServiceWSDLURL</term>
	///			<description>The Url where the WSDL maybe obtained</description>
	///		</item>
	///		<item>
	///			<term>ServiceName</term>
	///			<description>The name of the Web Service to invoke</description>
	///		</item>
	///		<item>
	///			<term>WebMethod</term>
	///			<description>The Web Method (opperation) to invoke.</description>
	///		</item>
	///		<item>
	///			<term>InputMessageTypeName/ContextProperty</term>
	///			<description>The type of the input message.(</description>
	///		</item>
	///		<item>
	///			<term>MessagePayload</term>
	///			<description>The path to the input data, note, this is the serialized object.</description>
	///		</item>
	///		<item>
	///			<term>ValidationStep</term>
	///			<description>Optional validation step.</description>
	///		</item>
	///	</list>
	///	</remarks>	
	public class SOAPHTTPRequestResponseStep: ITestStep
	{
		/// <summary>
		/// ITestStep.Execute() implementation
		/// </summary>
		/// <param name='testConfig'>The Xml fragment containing the configuration for this test step</param>
		/// <param name='context'>The context for the test, this holds state that is passed beteen tests</param>
		public void Execute(XmlNode testConfig, Context context)
		{
			const string SOAPPROXYNAMESPACE = "Microsoft.Services.BizTalkApplicationFramework.BizUnit.Proxy";
			string wsdlFile;
			string soapMessagePath;
			string inputMessageTypeName;
			string webMethod;
			string serviceName;
			string xmlRootAttribute;
			string defaultNamespace;
			Stream response = null;

			try
			{
				wsdlFile = context.ReadConfigAsString(testConfig, "WebServiceWSDLURL");
				soapMessagePath = context.ReadConfigAsString(testConfig, "MessagePayload");
				inputMessageTypeName = context.ReadConfigAsString(testConfig, "InputMessageTypeName");
				webMethod = context.ReadConfigAsString(testConfig, "WebMethod");
				serviceName = context.ReadConfigAsString(testConfig, "ServiceName");
				xmlRootAttribute = context.ReadConfigAsString(testConfig, "XmlRootAttribute");
				defaultNamespace = context.ReadConfigAsString(testConfig, "DefaultNamespace");

				Assembly proxyAssembly = GetProxyAssembly(wsdlFile, SOAPPROXYNAMESPACE);
				object objInputMessage = LoadMessage(proxyAssembly, SOAPPROXYNAMESPACE + "." + inputMessageTypeName, soapMessagePath, xmlRootAttribute, defaultNamespace);
			
				object proxy = Activator.CreateInstance(proxyAssembly.GetType(SOAPPROXYNAMESPACE + "." + serviceName));
				object proxy2 = Activator.CreateInstance(proxyAssembly.GetType(SOAPPROXYNAMESPACE + "." + serviceName));
				
				MethodInfo mi = proxy.GetType().GetMethod(webMethod);

				context.LogInfo("SOAPHTTPRequestResponseStep about to post data from File: {0} to the Service: {1} defined in WSDL: {2}", soapMessagePath, serviceName, wsdlFile);

				object outputMessage = mi.Invoke(proxy,new object[] {objInputMessage});

				if(null != outputMessage)
				{
					response = GetOutputStream(outputMessage);
					context.LogData("SOAPHTTPRequestResponseStep response data", response);
				}

				// Validate the response...
				try
				{
					context.ExecuteValidator( response, testConfig.SelectSingleNode("ValidationStep") );
				}
				catch(Exception e)
				{
					throw new ApplicationException("SOAPHTTPRequestResponseStep response stream was not correct!", e);
				}
			}
			finally
			{
				if ( null != response )
				{
					response.Close();
				}
			}
		}
	
		internal static Assembly GetProxyAssembly(string wsdlURI, string codeNamespace )
		{
			CSharpCodeProvider csProvider = new CSharpCodeProvider();
			WebClient client  = null; 
			Stream wsdlStream = null;
			client = new WebClient();
			string [] referenceAssemblies = new string[] {"system.dll",
															 "System.Xml.dll",
															 "System.Web.Services.dll"};
			wsdlStream = client.OpenRead(wsdlURI);
	
			ServiceDescription wsdl = ServiceDescription.Read(wsdlStream);
			ServiceDescriptionImporter wsdlImport = new ServiceDescriptionImporter();
			wsdlImport.AddServiceDescription(wsdl,null,null);
			
			CodeNamespace proxyClassNamespace = new CodeNamespace(codeNamespace );
			CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
			codeCompileUnit.Namespaces.Add(proxyClassNamespace);

			ServiceDescriptionImportWarnings warnings = wsdlImport.Import(proxyClassNamespace ,codeCompileUnit);
            CodeDomProvider myProvider = CodeDomProvider.CreateProvider("C#");
            
			CompilerParameters compileParam = new CompilerParameters(referenceAssemblies);
			compileParam.GenerateInMemory = false;
			compileParam.OutputAssembly = "TestFrameWorkProxy.dll";

			//CompilerResults compilerResults = codeCompiler.CompileAssemblyFromDom(compileParam,codeCompileUnit);
            CompilerResults compilerResults = myProvider.CompileAssemblyFromDom(compileParam, codeCompileUnit);
			if(compilerResults.Errors.HasErrors)
			{
				throw new Exception();
			}

			return compilerResults.CompiledAssembly;
		}

		internal static MemoryStream GetOutputStream(object outputMessage)
		{
			MemoryStream ms = new MemoryStream();
			XmlSerializer outputSerializer = new XmlSerializer(outputMessage.GetType());
			outputSerializer.Serialize(ms, outputMessage);
			ms.Seek(0, SeekOrigin.Begin);

			return ms;
		}

		internal static object LoadMessage(Assembly assembly, string msgTypeName, string messagePath, string xmlRootAttribute, string defaultNamespace)
		{
			XmlReader messageReader = null;
			System.Runtime.Remoting.ObjectHandle proxy = null;
			object message = null;
			XmlSerializer serializer = null;

			try
			{
				// This object requires CreateInstanceFrom...however the proxy object instantiation 
				// does not require CreateInstancefrom
				proxy = Activator.CreateInstanceFrom(assembly.Location, msgTypeName);
				messageReader = new XmlTextReader(messagePath);
				if (xmlRootAttribute == "")
					serializer = new XmlSerializer(proxy.Unwrap().GetType(), defaultNamespace);
				else
					serializer = new XmlSerializer(proxy.Unwrap().GetType(), new XmlAttributeOverrides(), new Type[0], new XmlRootAttribute(xmlRootAttribute), defaultNamespace);
				message = serializer.Deserialize(messageReader);
			}
			finally
			{
				if(messageReader != null)
				{
					messageReader.Close();
				}
			}

			return message;
		}
	}

}
